﻿using OnDash.Mathematics.Vector;

namespace OnDash.Mathematics.Geometry;

public struct Box3(Vector3 min, Vector3 max) : IEquatable<Box3>
{
    public float Left { get; set; }
    public float Right { get; set; }
    public float Top { get; set; }
    public float Bottom { get; set; }
    public float Front { get; set; }
    public float Behind { get; set; }

    private Vector3 _min = Vector3.ComponentMin(min, max);

    public Vector3 Min
    {
        get => _min;
        set
        {
            if (value.X > _max.X) _max.X = value.X;
            if (value.Y > _max.Y) _max.Y = value.Y;
            if (value.Z > _max.Z) _max.Z = value.Z;
            _min = value;
        }
    }

    private Vector3 _max = Vector3.ComponentMax(min, max);

    public Vector3 Max
    {
        get => _max;
        set
        {
            if (value.X < _min.X) _min.X = value.X;
            if (value.Y < _min.Y) _min.Y = value.Y;
            if (value.Z < _min.Z) _min.Z = value.Z;
            _max = value;
        }
    }

    public Box3(float minX, float minY, float minZ, float maxX, float maxY, float maxZ)
        : this(new Vector3(minX, minY, minZ), new Vector3(maxX, maxY, maxZ))
    {
    }

    public Vector3 Size
    {
        get => Max - Min;
        set
        {
            Vector3 center = Center;
            _min = center - (value * 0.5f);
            _max = center + (value * 0.5f);
        }
    }

    public static Box3 FromTlbrbf(float top, float left, float behind, float right, float bottom, float front) =>
        new(top, left, behind, right, bottom, front);

    public static Box3 FromDimensions(float left, float top, float behind, float width, float height, float depth) =>
        new(left, top, behind, left + width, top + height, behind + depth);

    public static Box3 FromDimensions(Vector3 position, Vector3 size) =>
        new(position.X, position.Y, position.Z, size.X, size.Y, size.Z);

    public Vector3 HalfSize
    {
        get => Size / 2;
        set => Size = value * 2;
    }

    public Vector3 Center
    {
        get => HalfSize + _min;
        set => Translate(value - Center);
    }

    public void Translate(Vector3 distance)
    {
        _min += distance;
        _max += distance;
    }

    public Box3 Translated(Vector3 distance)
    {
        var box = this;
        box.Translate(distance);
        return box;
    }

    public bool Contains(Vector3 point)
    {
        return _min.X < point.X && point.X < _max.X &&
               _min.Y < point.Y && point.Y < _max.Y &&
               _min.Z < point.Z && point.Z < _max.Z;
    }

    public bool ContainsInclusive(Vector3 point)
    {
        return _min.X <= point.X && point.X <= _max.X &&
               _min.Y <= point.Y && point.Y <= _max.Y &&
               _min.Z <= point.Z && point.Z <= _max.Z;
    }

    public bool ContainsExclusive(Vector3 point)
    {
        return _min.X < point.X && point.X < _max.X &&
               _min.Y < point.Y && point.Y < _max.Y &&
               _min.Z < point.Z && point.Z < _max.Z;
    }

    public bool Contains(Vector3 point, bool boundaryInclusive)
    {
        return boundaryInclusive ? ContainsInclusive(point) : ContainsExclusive(point);
    }

    public bool Contains(Box3 other)
    {
        return _max.X >= other._min.X && _min.X <= other._max.X &&
               _max.Y >= other._min.Y && _min.Y <= other._max.Y &&
               _max.Z >= other._min.Z && _min.Z <= other._max.Z;
    }

    public float DistanceToNearestEdge(Vector3 point)
    {
        var distX = new Vector3(
            MathHelper.Max(0f, MathHelper.Max(_min.X - point.X, point.X - _max.X)),
            MathHelper.Max(0f, MathHelper.Max(_min.Y - point.Y, point.Y - _max.Y)),
            MathHelper.Max(0f, MathHelper.Max(_min.Z - point.Z, point.Z - _max.Z)));
        return distX.Length;
    }

    public void Scale(Vector3 scale, Vector3 anchor)
    {
        _min = anchor + (_min - anchor) * scale;
        _max = anchor + (_max - anchor) * scale;
    }

    public Box3 Scaled(Vector3 scale, Vector3 anchor)
    {
        var box = this;
        box.Scale(scale, anchor);
        return box;
    }

    public void Inflate(Vector3 point)
    {
        _min = Vector3.ComponentMin(_min, point);
        _max = Vector3.ComponentMax(_max, point);
    }

    public Box3 Inflated(Vector3 point)
    {
        var box = this;
        box.Inflate(point);
        return box;
    }

    public static bool operator ==(Box3 left, Box3 right) => left.Equals(right);
    public static bool operator !=(Box3 left, Box3 right) => !left.Equals(right);

    public override bool Equals(object? obj) => obj is Box3 box3 && Equals(box3);

    public bool Equals(Box3 other) => _min.Equals(other._min) && _max.Equals(other._max);

    public override int GetHashCode() => HashCode.Combine(_min, _max);

    public override string ToString() => $"{Min} - {Max}";
}